﻿using System;
using System.Collections.Generic;
using System.Linq;
using Loki.Model;
using Luna.Data;
using Luna.Model;
using NUnit.Framework;
using Rhino.Mocks;

namespace Luna.Services.Test
{
    public abstract class EntityTest<TDataProvider, TEntity, TDataKey, TEntityData> : ServiceTest
        where TEntityData : ILunaData
        where TDataProvider : class
        where TEntity : class, ILunaEntity, IEditable<TEntity>
    {
        public EntityTest()
        {
            PropertyShifters = new List<IPropertyShifter<TEntity>>();
        }

        public override void SetUp()
        {
            base.SetUp();
            Console.WriteLine("Reset");
            var L_Temp = DataProvider.GetMockRepository();
            L_Temp.BackToRecordAll(BackToRecordOptions.Expectations);
            L_Temp.ReplayAll();
        }

        protected abstract TEntityData CreateMockData();

        protected abstract Func<TDataProvider, IEnumerable<TEntityData>> DataGetAll { get; }

        protected abstract Func<TDataProvider, TDataKey, TEntityData> DataGet { get; }

        protected abstract Action<TEntity> EntityDelete { get; }

        protected abstract Action<TDataProvider, TDataKey> DataDelete { get; }

        protected abstract Action<TEntity> EntitySave { get; }

        protected abstract Action<TDataProvider, TEntityData> DataSave { get; }

        protected abstract TDataProvider DataProvider { get; }

        protected abstract IEnumerable<TEntity> EntitiesAll { get; }

        protected abstract TEntity EntityGet(Guid P_Key);

        protected abstract TEntityData SaveMatch(TEntity P_Entity);

        protected abstract bool BuildMatch(TEntity P_Entity, TEntityData P_Data);

        protected IList<IPropertyShifter<TEntity>> PropertyShifters { get; private set; }

        protected abstract TDataKey DataKey(TEntityData P_Data);

        protected abstract bool DataKeyMatch(TDataKey P_Parameter, TEntity P_Match);

        [Test]
        public virtual void Save()
        {
            TEntity L_Result = ApplicationModel.Create<TEntity>();

            foreach (var L_Shifter in PropertyShifters)
            {
                L_Shifter.Shift(L_Result);
            }

            TEntityData L_Data = CreateMockData();
            L_Data.PK = L_Result.ID;

            DataProvider.Stub(x => DataGet(x, DataKey(L_Data))).Return(L_Data);

            EntitySave(L_Result);

            DataProvider.AssertWasCalled((p) => DataSave(p, SaveMatch(L_Result)));
        }

        [Test]
        public virtual void NewNotPersisted()
        {
            TEntity L_Result = ApplicationModel.Create<TEntity>();

            EntitySave(L_Result);

            DataProvider.AssertWasCalled((p) => DataSave(p, Arg<TEntityData>.Matches(a => a.AlreadyPersisted == false)));
        }

        [Test]
        public virtual void OldAlreadyPersisted()
        {
            TEntityData L_Data = CreateMockData();

            L_Data.AlreadyPersisted = true;

            TDataKey L_Key = DataKey(L_Data);
            DataProvider.Stub(x => DataGet(x, Arg<TDataKey>.Matches(a => Object.Equals(L_Key, a)))).Return(L_Data);

            TEntity L_Result = EntityGet(L_Data.PK);

            foreach (var L_Shifter in PropertyShifters)
            {
                L_Shifter.Shift(L_Result);
            }

            EntitySave(L_Result);

            DataProvider.AssertWasCalled((p) => DataSave(p, Arg<TEntityData>.Matches(a => a.AlreadyPersisted == true)));
        }

        [Test]
        public virtual void Delete()
        {
            TEntity L_Result = ApplicationModel.Create<TEntity>();

            EntityDelete(L_Result);

            DataProvider.AssertWasCalled(x => DataDelete(x, Arg<TDataKey>.Matches(a => DataKeyMatch(a, L_Result))));
        }

        [Test]
        public virtual void NotSaved()
        {
            TEntity L_Result = ApplicationModel.Create<TEntity>();

            L_Result.AcceptChanges();

            DataProvider.Expect(s => DataSave(s, Arg<TEntityData>.Is.Anything)).Repeat.Never();
            EntitySave(L_Result);
            DataProvider.VerifyAllExpectations();

            //DataProvider.AssertWasNotCalled(x => DataSave(x, Arg<TEntityData>.Is.Anything));
        }

        [Test]
        public void Equatable()
        {
            TEntity L_Result = ApplicationModel.Create<TEntity>();
            TEntity L_Result2 = ApplicationModel.Create<TEntity>();

            Expect(Object.Equals(L_Result, L_Result2), Is.False);

            L_Result2.ID = L_Result.ID;

            Expect(Object.Equals(L_Result, L_Result2), Is.True);
            Expect(Object.Equals(L_Result, null), Is.False);
            Expect(Object.Equals(null, L_Result2), Is.False);
            Expect(Object.Equals(new object(), L_Result), Is.False);
        }

        [Test]
        public void Get()
        {
            TEntityData L_Data = CreateMockData();
            TDataKey L_Key = DataKey(L_Data);
            DataProvider.Stub(x => DataGet(x, Arg<TDataKey>.Matches(a => Object.Equals(L_Key, a)))).Return(L_Data);

            TEntity L_Result = EntityGet(L_Data.PK);
            Expect(BuildMatch(L_Result, L_Data), Is.True);
        }

        [Test]
        public void GetAll()
        {
            TEntityData L_Data1 = CreateMockData();
            TEntityData L_Data2 = CreateMockData();

            DataProvider.Stub(p => DataGetAll(p)).Return(new[] { L_Data1, L_Data2 });

            Expect(EntitiesAll.Count(), Is.EqualTo(2));

            TEntity L_Result = EntityGet(L_Data2.PK);
            Expect(BuildMatch(L_Result, L_Data2), Is.True);
        }

        [Test]
        public void DirtyWhenCreated()
        {
            TEntity L_Result = ApplicationModel.Create<TEntity>();

            Expect(L_Result.IsChanged, Is.True);
            L_Result.AcceptChanges();
            Expect(L_Result.IsChanged, Is.False);
        }

        [Test]
        public void CleanWhenCleanCopied()
        {
            TEntity L_Result = ApplicationModel.Create<TEntity>();

            foreach (var L_Shifter in PropertyShifters)
            {
                L_Shifter.Shift(L_Result);
            }

            L_Result.AcceptChanges();

            TEntity L_Copied = L_Result.EditableCopy;

            Expect(L_Copied.IsChanged, Is.False);
        }

        [Test]
        public void DirtyWhenDirtyCopied()
        {
            TEntity L_Result = ApplicationModel.Create<TEntity>();

            foreach (var L_Shifter in PropertyShifters)
            {
                L_Shifter.Shift(L_Result);
            }

            TEntity L_Copied = L_Result.EditableCopy;

            Expect(L_Copied.IsChanged, Is.True);
        }

        [Test]
        public void DirtyWhenModified()
        {
            TEntity L_Result = ApplicationModel.Create<TEntity>();
            L_Result.AcceptChanges();
            foreach (var L_Shifter in PropertyShifters)
            {
                L_Shifter.Shift(L_Result);
                Expect(L_Result.IsChanged, Is.True);
                L_Result.AcceptChanges();
            }
        }

        [Test]
        public void StableWhenIso()
        {
            TEntity L_Result = ApplicationModel.Create<TEntity>();
            L_Result.AcceptChanges();
            foreach (var L_Shifter in PropertyShifters)
            {
                L_Shifter.SameValue(L_Result);
                Expect(L_Result.IsChanged, Is.False);
            }
        }
    }
}